//
//  MinePlayer.swift
//  ContactPoster
//
//  Created by TSYH on 2024/1/18.
//

import Foundation
import AVFoundation
import ExtensionsKit

public protocol MinePlayerDelegate: NSObjectProtocol {
    func player(_ player: MinePlayer, resourceChanged resource: BMPlayerResource)
    func player(_ player: MinePlayer, stateChanged state: MinePlayerState)
    func player(_ player: MinePlayer, progressChanged current: Double, total: Double)
    func player(_ player: MinePlayer, loadedTimeDidChange duration: TimeInterval, total: TimeInterval)
}

// 播放状态
public enum MinePlayerState: String {
    case unknow
    case idle
    case loading
    case readyToPlay
    case pause
    case playing
    case playEnd
    case fail
}

public class MinePlayer: NSObject {
    
    private lazy var listeners = NSHashTable<AnyObject>(options: .weakMemory)
    public func add<T>(_ listener: T) where T: MinePlayerDelegate {
        listeners.add(listener)
    }
    var observers: [MinePlayerDelegate] {
        return listeners.allObjects as! [MinePlayerDelegate]
    }
    
    private(set) var player: AVPlayer?
    private var timeObserver: Any?
    
    public  var state: MinePlayerState = .unknow {
        didSet {
            Log("===state: \(state.rawValue)")
            if state == .playing {
                player?.rate = rate
            }
            if oldValue != state {
                observers.forEach({ $0.player(self, stateChanged: state) })
            }
        }
    }
    
    private var currentPlayerItem: AVPlayerItem? {
        willSet {
            // 移除旧值监听
            removePlayItemObserver()
        }
        didSet {
            guard let item = currentPlayerItem else { return }
            if player == nil {
                player = AVPlayer(playerItem: item)
                player?.automaticallyWaitsToMinimizeStalling = false
                addPlayerObserver()
            } else {
                player?.replaceCurrentItem(with: item)
            }
            // 添加新值监听
            addPlayItemObserver()
        }
    }
    
    lazy var shouldAutoPlayAfterLoad = true
    lazy var isBuffering = false
    lazy var seekTargetTime: TimeInterval = 0
    
    public lazy var rate: Float = 1.0 {
        didSet {
            player?.rate = rate
            if state == .pause {
                state = .playing
            }
        }
    }
    
    public var currenPlayingUrl : URL? {
        if let playerItem = currentPlayerItem,
           let asset = playerItem.asset as? AVURLAsset {
            return asset.url
        }
        return nil
    }
}

// MARK: 播放
public extension MinePlayer {
    func setResource(_ resource: BMPlayerResource) {
        let asset = resource.definitions[0]
        playAsset(asset: asset.avURLAsset)
        observers.forEach({ $0.player(self, resourceChanged: resource) })
    }
    
    func playURL(url: URL) {
        let asset = AVURLAsset(url: url)
        playAsset(asset: asset)
    }
    
    func playAsset(asset: AVURLAsset) {
        // 忽略系统静音
        try? AVAudioSession.sharedInstance().setCategory(.playback)
        try? AVAudioSession.sharedInstance().setActive(true)
        
        let playerItem = AVPlayerItem(asset: asset)
        currentPlayerItem = playerItem
        
        play()
    }
    
    func play() {
        guard let currentItem = player?.currentItem else {
            return
        }
        shouldAutoPlayAfterLoad = true
        
        if player?.status != .readyToPlay || !currentItem.isPlaybackLikelyToKeepUp {
            state = .loading
        }
//        DispatchQueue.main.asyncAfter(deadline: .now()+2) {
            self.player?.play()
            self.player?.rate = self.rate
//        }
        
        if currentItem.isPlaybackLikelyToKeepUp || state == .pause {
            state = .playing
        }
    }
    
    func pause() {
        player?.pause()
        state = .pause
        shouldAutoPlayAfterLoad = false
    }
    
    func togglePlay() {
        if state == .playEnd {
            seek(0, completion: nil)
            play()
        }
        else if state == .playing {
            pause()
        } else {
            play()
        }
    }
    
    func seek(_ toTime: TimeInterval, completion: (() -> Void)?) {
        guard toTime >= 0 else {
            return
        }
        if player?.currentItem?.status == .readyToPlay {
            player?.seek(to: CMTime(value: CMTimeValue(toTime), timescale: CMTimeScale(1)), toleranceBefore: CMTime(seconds: 1, preferredTimescale: 1), toleranceAfter: CMTime(seconds: 1, preferredTimescale: 1), completionHandler: { finished in
                completion?()
            })
        } else {
            self.seekTargetTime = toTime
        }
    }
    
    /// 音量
    var volum: Float {
        set {
            player?.volume = newValue
        }
        get {
            return player?.volume ?? 0
        }
    }
}

// MARK: 状态监听
extension MinePlayer {
    func addPlayerObserver() {
        guard let player = player else {
            return
        }
        // 0.1秒调用一次
        let timeI = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        timeObserver = player.addPeriodicTimeObserver(forInterval: timeI, queue: .main, using: { [weak self] time in
            guard let playerItem = self?.player?.currentItem,
                  time != .indefinite,
                  playerItem.duration != .indefinite else {
                return
            }
            let current = CMTimeGetSeconds(time)
            let total = CMTimeGetSeconds(playerItem.duration)
            self?.observers.forEach({ $0.player(self!, progressChanged: Double(current), total: Double(total)) })
        })
    }
    
    func addPlayItemObserver() {
        guard let playerItem = player?.currentItem else {
            return
        }
        // 监控状态属性，注意AVPlayer也有一个status属性，通过监控它的status也可以获得播放状态
        playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
        // 监控网络加载情况属性
        playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        // 缓冲区空了，需要等待数据
        playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
        // 缓冲区有足够数据可以播放了
        playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
        
        // 给AVPlayerItem添加播放完成通知
        NotificationCenter.default.addObserver(self, selector: #selector(playbackFinished(_:)), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
    }
    
    func removePlayItemObserver() {
        guard let playerItem = player?.currentItem else {
            return
        }
        playerItem.removeObserver(self, forKeyPath: "status")
        playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
        playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty")
        playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
        NotificationCenter.default.removeObserver(self)
    }
    
    @objc func playbackFinished(_ object: Any) {
        state = .playEnd
    }
    
    public override func observeValue(forKeyPath keyPath: String?,
                                     of object: Any?,
                                     change: [NSKeyValueChangeKey : Any]?,
                                     context: UnsafeMutableRawPointer?) {
        guard let playItem = object as? AVPlayerItem, let keyPath = keyPath, playItem == currentPlayerItem else {
            return
        }
        switch keyPath {
        case "status":
//            Log("===state: \(playItem.status.rawValue)")
            switch playItem.status {
            case .readyToPlay:
                state = .readyToPlay
                    if shouldAutoPlayAfterLoad {
                        player?.rate = rate
                        if playItem.isPlaybackLikelyToKeepUp {
                            state = .playing
                            player?.play()
                        } else {
                            DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
                                self.state = .playing
                                self.player?.play()
                            }
                        }
                    }
                if seekTargetTime > 0 {
                    seek(seekTargetTime) { [weak self] in
                        self?.seekTargetTime = 0
                    }
                }
                
            case .unknown, .failed:
                state = .fail
                Log("===Fail: \(String(describing: playItem.error?.localizedDescription))")
            default:
                break
            }
            
        case "loadedTimeRanges":
            // 计算缓冲进度
            if let timeRange = playItem.loadedTimeRanges.first?.timeRangeValue {
                let buffer = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration)
                observers.forEach({ $0.player(self, loadedTimeDidChange: buffer, total: CMTimeGetSeconds(playItem.duration)) })
            }
            
        case "playbackBufferEmpty":
            // 当缓冲是空的时候
            if playItem.isPlaybackBufferEmpty {
                startLoadWhenBufferEmpty(for: playItem)
            }
            
        case "playbackLikelyToKeepUp":
            if !playItem.isPlaybackLikelyToKeepUp {
                startLoadWhenBufferEmpty(for: playItem)
            }
            
        default:
            break
        }
    }
    
    // 缓冲不足时，暂停一会儿，加载
    // 需要先暂停一小会之后再播放，否则网络状况不好的时候时间在走，声音播放不出来
    func startLoadWhenBufferEmpty(for playItem: AVPlayerItem) {
        player?.pause()
        state = .pause
        
        bufferingSomeSecond(for: playItem)
    }
    
    /**
     缓冲比较差的时候
     */
    private func bufferingSomeSecond(for playItem: AVPlayerItem) {
        guard let currentItem = currentPlayerItem, playItem == currentItem else {
            return
        }
        // playbackBufferEmpty会反复进入，因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
        if isBuffering || playItem.isPlaybackLikelyToKeepUp { return }
        isBuffering = true
        
        self.state = .loading
        
        let popTime = DispatchTime.now() + Double(Int64( Double(NSEC_PER_SEC) * 1.0 )) / Double(NSEC_PER_SEC)
        DispatchQueue.main.asyncAfter(deadline: popTime) {[weak self, weak playItem] in
            guard let `self` = self, let playItem = playItem else { return }
            // 如果执行了play还是没有播放则说明还没有缓存好，则再次缓存一段时间
            self.isBuffering = false
            if !playItem.isPlaybackLikelyToKeepUp {
                self.bufferingSomeSecond(for: playItem)
            } else {
                // 如果此时用户已经暂停了，则不再需要开启播放了
                if self.shouldAutoPlayAfterLoad {
                    player?.play()
                    self.state = .playing
                } else {
                    self.state = .readyToPlay
                }
            }
        }
    }
}
